package io.gitee.zhangbinhub.admin.oauth.controller.api

import cn.dev33.satoken.annotation.SaCheckRole
import cn.dev33.satoken.stp.StpUtil
import io.gitee.zhangbinhub.acp.boot.exceptions.WebException
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.acp.cloud.resource.server.tools.TokenTools
import io.gitee.zhangbinhub.acp.cloud.tools.CloudTools
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.admin.common.api.OauthApi
import io.gitee.zhangbinhub.admin.common.constant.RoleCode
import io.gitee.zhangbinhub.admin.common.vo.InfoVo
import io.gitee.zhangbinhub.admin.oauth.constant.OauthConstant
import io.gitee.zhangbinhub.admin.oauth.service.ApplicationService
import io.gitee.zhangbinhub.admin.oauth.service.UserService
import io.gitee.zhangbinhub.admin.oauth.vo.OnlineInfoVo
import io.gitee.zhangbinhub.admin.resource.server.base.BaseResourceServerController
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.util.LinkedMultiValueMap
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import org.springframework.web.client.HttpClientErrorException
import org.springframework.web.client.RestClient

/**
 * @since JDK 11
 */
@Validated
@RestController
@RequestMapping(OauthApi.basePath)
@Tag(name = "认证信息")
class TokenController @Autowired
constructor(
    private val logAdapter: LogAdapter,
    private val cloudTools: CloudTools,
    private val applicationService: ApplicationService,
    private val userService: UserService,
    @param:Qualifier("acpSpringCloudResourceServerRestClientBuilder")
    private val restClientBuilder: RestClient.Builder,
    @param:Value($$"${spring.application.name}")
    private val applicationName: String
) : BaseResourceServerController(logAdapter) {
    @Throws(WebException::class)
    private fun innerToken(paramMap: MutableMap<String, Any>): ResponseEntity<String> {
        val clientId = paramMap[OauthConstant.clientIdKey]?.toString()
        if (CommonTools.isNullStr(clientId)) {
            throw WebException("${OauthConstant.clientIdKey} 不能为空")
        }
        val clientSecret = paramMap[OauthConstant.clientSecretKey]?.toString()
        if (CommonTools.isNullStr(clientSecret)) {
            throw WebException("${OauthConstant.clientSecretKey} 不能为空")
        }
        val requestBody = LinkedMultiValueMap<String, Any>().apply {
            paramMap.forEach { entry ->
                this.add(entry.key, entry.value)
            }
        }
        return try {
            restClientBuilder.build().post().uri("http://$applicationName/inner/oauth/token")
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .headers { headers ->
                    cloudTools.transmitHeadersServlet(headers, null, false)
                }
                .body(requestBody)
                .retrieve()
                .toEntity(String::class.java)
        } catch (e: HttpClientErrorException) {
            ResponseEntity.status(e.statusCode).body(e.responseBodyAsString)
        } catch (e: Exception) {
            throw WebException(HttpStatus.UNAUTHORIZED, e.message)
        }
    }

    @Operation(summary = "申请token")
    @PostMapping(
        value = [OauthApi.token],
        consumes = [MediaType.APPLICATION_JSON_VALUE],
        produces = [MediaType.APPLICATION_JSON_VALUE]
    )
    @Throws(WebException::class)
    fun tokenFromJson(@RequestBody paramMap: MutableMap<String, Any>): ResponseEntity<String> = innerToken(paramMap)

    @Operation(summary = "申请token")
    @PostMapping(
        value = [OauthApi.token],
        consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
        produces = [MediaType.APPLICATION_JSON_VALUE]
    )
    @Throws(WebException::class)
    fun tokenFromForm(@RequestParam paramMap: MutableMap<String, Any>): ResponseEntity<String> = innerToken(paramMap)

    @Operation(summary = "注销当前用户")
    @PostMapping(value = [OauthApi.logOut], produces = [MediaType.APPLICATION_JSON_VALUE])
    @Throws(WebException::class)
    fun doLogOut(): ResponseEntity<InfoVo> =
        try {
            logAdapter.info("用户[loginNo=" + StpUtil.getLoginIdAsString() + "]主动下线!")
            TokenTools.revokeAccessToken()
            ResponseEntity.ok(InfoVo(message = "成功下线"))
        } catch (e: Exception) {
            throw WebException(e.message)
        }

    @Operation(summary = "获取各应用在线用户数统计")
    @GetMapping(value = [OauthApi.onlineInfo], produces = [MediaType.APPLICATION_JSON_VALUE])
    @Throws(WebException::class)
    fun getOnlineInfo(): ResponseEntity<List<OnlineInfoVo>> =
        try {
            val infoList = StpUtil.searchTokenValue("", 0, -1, true).map { tokenValue ->
                StpUtil.getTerminalInfoByToken(tokenValue.replace(StpUtil.stpLogic.splicingKeyTokenValue(""), ""))
            }
            mutableListOf<OnlineInfoVo>().apply {
                applicationService.getOwnAppList(StpUtil.getLoginIdAsString()).forEach {
                    this.add(
                        OnlineInfoVo(
                            appId = it.id,
                            appName = it.appName,
                            count = infoList.filter { info -> info?.deviceType == it.id }.size.toLong()
                        )
                    )
                }
            }.let {
                ResponseEntity.ok(it)
            }
        } catch (e: Exception) {
            throw WebException(e.message)
        }

    @Operation(summary = "指定应用下的用户强制下线")
    @ApiResponses(ApiResponse(responseCode = "400", description = "参数校验不通过；没有权限做此操作；"))
    @SaCheckRole(RoleCode.SUPER)
    @DeleteMapping(value = [OauthApi.onlineInfo + "/{appId}"], produces = [MediaType.APPLICATION_JSON_VALUE])
    @Throws(WebException::class)
    fun delete(
        @Parameter(description = "应用id", required = true)
        @PathVariable(name = "appId")
        appId: String,
        @Parameter(description = "用户id列表", required = true)
        @NotEmpty(message = "id不能为空")
        @NotNull(message = "id不能为空")
        @RequestBody
        idList: List<String>
    ): ResponseEntity<InfoVo> =
        try {
            idList.forEach {
                val userInfo = userService.getUserInfoById(it) ?: throw WebException("找不到该用户信息")
                TokenTools.revokeAccessToken(appId, userInfo.loginNo)
                logAdapter.info("用户[" + userInfo.name + "(" + userInfo.loginNo + ")]被管理员强制下线!")
            }
            ResponseEntity.ok(InfoVo(message = "成功下线"))
        } catch (e: Exception) {
            throw WebException(e.message)
        }
}
